﻿using System.Linq.Expressions;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Reflection;
using System;
using EmbeddedLanguage.Attributes;

namespace EmbeddedLanguage
{
    public partial class ExpressionToJSProcessor : ExpressionVisitor
    {

        public ExpressionToJSProcessor() { }

        #region State
        public void TransferState(ExpressionToJSProcessor copy)
        {
            copy.Function = this.Function;
            copy.CurrentNestedString = this.CurrentNestedString;
            copy.Counter = this.Counter;
            copy.ParameterSubstitutions = this.ParameterSubstitutions.Clone().MergeWith(copy.ParameterSubstitutions);
            if(this.KeyVariableName!=null && copy.KeyVariableName == null) copy.KeyVariableName = this.KeyVariableName;
        }

        public JSFunction Function;

        public NestedString CurrentNestedString;

        public int Counter;

        public Dictionary<string, string> ParameterSubstitutions;

        public Dictionary<string, string> ParameterSubstitutionsNN
        {
            get
            {
                if (ParameterSubstitutions == null) ParameterSubstitutions = new Dictionary<string, string>();
                return ParameterSubstitutions;
            }
        }

        public string KeyVariableName;

        #endregion

        

        

        #region private util methods

        public static string GetFullName(Type typ)
        {
            string classPath = typ.FullName;
            var nsAttrib = typ.GetAttribute<JSNamespaceSubstitionAttribute>();
            if (nsAttrib != null)
            {
                classPath = classPath.Replace(typ.Namespace, nsAttrib.Namespace);
            }

            classPath = classPath.Replace("+", ".");
            classPath = classPath.SubstringBefore("`");
            return classPath;
        }

        protected virtual List<string> GetNames(ReadOnlyCollection<ParameterExpression> parameters)
        {
            if (parameters == null) return new List<string>();
            var list = new List<string>();
            foreach (var param in parameters)
            {
                list.Add(param.Name);
            }
            return list;
        }

        protected NestedString GetPreviousSibling(NestedString ns)
        {
            var parent = ns.Parent;
            if (parent == null) return null;
            int len = parent.Children == null ? 0 : parent.Children.Count;
            for (int i = len - 1; i >= 0; i--)
            {
                if (parent.Children[i] == ns)
                {
                    if (i > 0)
                    {
                        return parent.Children[i - 1];
                    }
                    else
                    {
                        return null;
                    }
                }
            }
            return null;
        }

        protected NestedString FindKabooseInside(NestedString ns)
        {
            if (ns == null) return null;
            if (ns.IsKaboose) return ns;
            if (ns.Children != null)
            {
                foreach (var child in ns.Children)
                {
                    var kaboose = this.FindKabooseInside(child);
                    if (kaboose != null) return kaboose;
                }
            }
            return null;
        }

        protected NestedString DetermineIfKaboose(NestedString ns)
        {
            //NestedString returnObj = null;
            var prevSibling = this.GetPreviousSibling(ns);
            var kaboose = this.FindKabooseInside(prevSibling);
            if (kaboose !=null)
            {
                ns.Footer = kaboose.Footer;
                kaboose.Footer = null;
                kaboose.IsKaboose = false;
                ns.IsKaboose = true;
                ns.TrainOfMethodsParameter = prevSibling.TrainOfMethodsParameter;
                ns.ReturningParent = prevSibling.ReturningParent;
                return kaboose;
            }
            NestedString returnObj = null;
            var ancestor = ns.Parent;
            while (ancestor != null)
            {
                if (this.IsReturnNS(ancestor))
                {
                    ns.IsKaboose = true;
                    ancestor.RHS = null;
                    ns.ReturningParent = ancestor;
                    break;
                }
                if (ancestor.IsKaboose && ancestor.IsKabooseTransferrableToChild)
                {
                    ancestor.IsKaboose = false;
                    ns.IsKaboose = true;
                    ns.ReturningParent = ancestor.ReturningParent;
                    ancestor.ReturningParent = null;
                    returnObj = ancestor;
                    break;
                }
                if (!ancestor.HasHeader && !ancestor.HasLHS)
                {
                    ancestor = ancestor.Parent;
                }
                else
                {
                    ancestor = null;
                }
            }
            return returnObj;
        }

        private bool IsReturnNS(NestedString ns)
        {
            if (ns == null) return false;
            return ns.LHS == "return " && ns.RHS == ";";
        }

        protected bool RemoveReturnFromParent(NestedString ns)
        {
            var pointer = ns;
            while (pointer != null)
            {
                if (this.IsReturnNS(pointer))
                {
                    pointer.LHS = null;
                    pointer.RHS = null;
                    return true;
                }
                if (!string.IsNullOrEmpty(pointer.LHS) || !string.IsNullOrEmpty(pointer.RHS)
                    || !string.IsNullOrEmpty(pointer.Header))
                {
                    return false;
                }
                pointer = pointer.Parent;
            }
            return false;
        }

        protected string CreateNewVariableName()
        {
            return ObjectEx.RootNS + "_" + this.Counter++;
        }
        #endregion
    }

    
}
